﻿
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Device.Location;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Controls.Maps;
using OttawaTourMap.Bing.Route;
using OttawaTourMap.Helpers;
using Microsoft.Phone.Controls.Maps.Core;
using OttawaTourMap.Models;
using Microsoft.Phone.Controls.Maps.Platform;


namespace OttawaTourMap
{
    public partial class MainPage : PhoneApplicationPage
    {
        #region Consts

        /// <value>Default map zoom level.</value>
        private const double DefaultZoomLevel = 18.0;

        /// <value>Maximum map zoom level allowed.</value>
        private const double MaxZoomLevel = 21.0;

        /// <value>Minimum map zoom level allowed.</value>
        private const double MinZoomLevel = 1.0;
                                     
        #endregion



        #region Fields

        //This keeps track of user route
        private UserTraceModel traceUser;

        //TODO : Use this later
        public RouteResults compelteTourResult;
        public RouteResults shortTourResult; // Contains the lcoation sorted in order of distance from the user location
       // public int tourCurrentIndex = 0;

        /// <value>Provides credentials for the map control.</value>
        private readonly CredentialsProvider _credentialsProvider = new ApplicationIdCredentialsProvider(App.Id);

        /// <value>Default location coordinate.</value>\
        /// This location will be updated every time with the start location of the tour
        private static GeoCoordinate DefaultLocation = new GeoCoordinate(45.424585, -75.68608); // start with tabret hall - Default location

        private double userCurrentLocationLatitide;
        private double userCurrentLocationLongitude;

        public double UserCurrentLocationLatitude { 
            get
            {
                return userCurrentLocationLatitide;
            }
            set
            {
                if (value != userCurrentLocationLatitide)
                    userCurrentLocationLatitide = value;
            }
        }
        public double UserCurrentLocationLongitude 
        {
            get
            {
                return userCurrentLocationLongitude;
            }
            set
            {
                if (value != userCurrentLocationLongitude)
                    userCurrentLocationLongitude = value;
            }
        }

        public void setDefaultLocation(GeoCoordinate newLocation)
        {
            DefaultLocation = newLocation;
        }

        
        /// <value>Collection of pushpins available on map.</value>
        /// The dafault is the start location of the tour
        /// User location
        private readonly ObservableCollection<PushpinModel> _pushpins = new ObservableCollection<PushpinModel>();
        /*{
            new PushpinModel
            {
                Location = DefaultLocation,
                Icon = new Uri("/Resources/Icons/Pushpins/PushpinBicycle.png", UriKind.Relative),
                isUserLocation = true
            }
        };*/

        /// <value>Collection of calculated map routes.</value>
        private readonly ObservableCollection<RouteModel> _routes = new ObservableCollection<RouteModel>();


        /// <value>Collection of user route points.</value>
        private readonly ObservableCollection<RouteModel> _userroutes = new ObservableCollection<RouteModel>();

        /// <value>Collection of calculated map routes itineraries.</value>
        private readonly ObservableCollection<ItineraryItem> _itineraries = new ObservableCollection<ItineraryItem>();

        /// <value>Map zoom level.</value>
        private double _zoom;

        /// <value>Map center coordinate.</value>
        /// This will be the start location of the tour
        private GeoCoordinate _center;

        // List of coordiate points for the tour
        List<String> coordinateList { get; set; }

        /// <summary>
        /// This list holds the coordiante location of the nearest tour point and the next one to that
        /// A toggel button is used to draw the complete route and a short version of the route
        /// </summary>

        List<String> shortTourList { get; set; }

        GeoCoordinateWatcher _geoCoordinateWatcher;

        public ItenariesDirectionModel itenariesDirectionModel = null;

        #endregion

        #region Properties

        public bool toggleCompleteTour{ get; set; }

        public bool HasDirections
        {
            get { return Itineraries.Count > 0; }
        }

        /// <summary>
        /// Gets the credentials provider for the map control.
        /// </summary>
        public CredentialsProvider CredentialsProvider
        {
            get { return _credentialsProvider; }
        }

        /// <summary>
        /// Gets or sets the map zoom level.
        /// </summary>
        public double Zoom
        {
            get { return _zoom; }
            set
            {
                var coercedZoom = Math.Max(MinZoomLevel, Math.Min(MaxZoomLevel, value));
                if (_zoom != coercedZoom)
                {
                    _zoom = value;
                    NotifyPropertyChanged("Zoom");
                }
            }
        }

        /// <summary>
        /// Gets or sets the map center location coordinate.
        /// </summary>
        public GeoCoordinate Center
        {
            get { return _center; }
            set
            {
                if (_center != value)
                {
                    _center = value;
                    NotifyPropertyChanged("Center");
                }
            }
        }


        private ObservableCollection<Location> routeLocationPoints = new ObservableCollection<Location>();

        public ObservableCollection<Location> routePoints
        {
            get { return routeLocationPoints; }
        }

        /// <summary>
        /// Gets a collection of pushpins.
        /// </summary>
        public ObservableCollection<PushpinModel> Pushpins
        {
            get { return _pushpins; }
        }

        /// <summary>
        /// Gets a collection of routes.
        /// </summary>
        public ObservableCollection<RouteModel> Routes
        {
            get { return _routes; }
        }

        public ObservableCollection<RouteModel> UserRoutes
        {
            get { return _userroutes; }
        }

        public RouteModel userRouteModel = new RouteModel();

        public ObservableCollection<UserTraceModel> traceUserRoute { get; set; }
        
        /// <summary>
        /// Gets a collection of route itineraries.
        /// </summary>
        public ObservableCollection<ItineraryItem> Itineraries
        {
            get { return _itineraries; }
        }

        /// <value>Collection of calculated map routes information.</value>
        private ObservableCollection<DirectionModel> _directionInformation = new ObservableCollection<DirectionModel>();
        /// <summary>
        /// Gets a collection of direction informations itineraries.
        /// </summary>
        public ObservableCollection<DirectionModel> DirectionInformations
        {
            get { return _directionInformation; }
        }

        /// <summary>
        /// Gets or sets the route destination location.
        /// </summary>
        public static string To { get; set; }

        /// <summary>
        /// Gets or sets the route origin location.
        /// </summary>
        public static string From { get; set; }

        #endregion

        #region Tasks

        private void InitializeDefaults()
        {
            Zoom = DefaultZoomLevel;
            Center = DefaultLocation;

            //This list will be repalced by model class
            coordinateList = new List<string>();
            shortTourList = new List<string>();
            userCurrentLocationLatitide = 45.424585;
            userCurrentLocationLongitude = -75.68608;
            toggleCompleteTour = false;
            compelteTourResult = null;
            shortTourResult = null;

            itenariesDirectionModel = new ItenariesDirectionModel();

            
           
        }


               
        private void ChangeMapMode()
        {
            if (Map.Mode is AerialMode)
            {
                Map.Mode = new RoadMode();
            }
            else
            {
                Map.Mode = new AerialMode(true);
            }
        }

        private void CenterLocation()
        {
            // Center map to default location.
            Center = DefaultLocation;

            // Reset zoom default level.
            Zoom = DefaultZoomLevel;
        }

        private void CenterPushpinsPopup(Point touchPoint)
        {
            // Reposition the pushpins popup to the center of the touch point.
            Canvas.SetTop(PushpinPopup, touchPoint.Y - ListBoxPushpinCatalog.Height / 2);
        }

        private void CreateNewPushpin(object selectedItem, Point point)
        {
            // Translate the map viewport touch point to a geo coordinate.
            GeoCoordinate location;
            Map.TryViewportPointToLocation(point, out location);

            // Use the geo coordinate calculated to add a new pushpin,
            // based on the selected pushpin prototype,
            var pushpinPrototype = selectedItem as PushpinModel;
            var pushpin = pushpinPrototype.Clone(location);
            Pushpins.Add(pushpin);
        }

       


        //This funtion draws the route based on the list of coordiantes porvided
        private void CalculateRouteWithCoordinates(List<String> tourPoints)
        {
            
            try
            {
               
                //TODO : To handle option to draw route with major roads. To explore the API - CalculateRoutesFromMajorRoads 

                var routeCalculator = new RouteCalculator(
                    CredentialsProvider,
                    tourPoints,
                    Dispatcher,
                    result =>
                    {
                        // Clear the route collection to have only one route at a time.
                        Routes.Clear();

                        // Clear previous route related itineraries.
                        Itineraries.Clear();

                        // Create a new route based on route calculator result,
                        // and add the new route to the route collection.
                        var routeModel = new RouteModel(result.Result.RoutePath.Points);
                        Routes.Add(routeModel);

                                             
                        // Add new route itineraries to the itineraries collection.
                        int i = 1;
                        foreach (var routeLegs in result.Result.Legs)
                        {

                            foreach (var itineraryItem in routeLegs.Itinerary)
                            {
                                Itineraries.Add(itineraryItem);
                                _directionInformation.Add(new DirectionModel(itineraryItem.Text,itineraryItem.Summary.Distance.ToString() + " mi",i.ToString(),
                                    itineraryItem.Summary.BoundingRectangle));
                                i++;
                            }
                        }

                        /*
                         * For Hiding the small blue shapes in the map
                         * for (var i = 0; i < legs.length; i++) {
                            leg = legs[i];
                            legItinerary = leg.Itinerary;
    
                            for (var j = 0; j < legItinerary.Items.length; j++) {
                                  legItinerary.Items[j].Shape.Hide(); 
                            }
                        }

                         */

                        //Store the result, so that we dont need to calculate it all the time
                        if (!toggleCompleteTour)
                        {
                            compelteTourResult = new RouteResults(result.Result.RoutePath.Points, result.Result.Legs);
                        }
                        else {

                            shortTourResult = new RouteResults(result.Result.RoutePath.Points, result.Result.Legs);
                        }

                        // Set the map to center on the new route.
                        //TODO : Change this to point to the start location of the tour
                        var viewRect = LocationRect.CreateLocationRect(routeModel.Locations);
                        Map.SetView(viewRect);

                        ShowDirectionsView();

                        //After the route is drawn, Add pushpins to the locations
                        CreateNewPushpinForTourLocations(tourPoints);
                        
                    });

                // Display an error message in case of fault.
                routeCalculator.Error += r => MessageBox.Show(r.Reason);

                // Start the route calculation asynchronously.
                routeCalculator.CalculateAsync();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void CreateNewPushpinForTourLocations(List<string> routes)
        {
            //remove all pushpins
            Pushpins.Clear();
            int i = 0;
            foreach (var location in routes)
            {
                double latitude = double.Parse(location.Split('$')[0]);
                double longitude = double.Parse(location.Split('$')[1]);
                var newPushPinModel = new PushpinModel
                {
                    Location = new GeoCoordinate(latitude,longitude),
                    Icon = new Uri("/Resources/Icons/ApplicationBar/Location.png", UriKind.Relative),
                    Text = i.ToString(),
                    isUserLocation = true,
                    TypeName = "Tour Point",
                    isVisited = false,
                    Visited = "White",
                    Opacity = "1"
                };

                Pushpins.Add(newPushPinModel);
                i++;
            }

           
        }


        private int currentIndexOfTourPoint = 0;
        private double currentDistanceFromTourStop = -1;
        private List<int> indexOfVisitedTourStops = new List<int>();

        private void trackUserMovement()
        {
            //1. Get the user current information
            GeoCoordinate userLocation = new GeoCoordinate(UserCurrentLocationLatitude, UserCurrentLocationLongitude);

            //2. Get the location from the curernt index
            double latitude = double.Parse(shortTourList[currentIndexOfTourPoint].Split('$')[0]);
            double longitude = double.Parse(shortTourList[currentIndexOfTourPoint].Split('$')[1]);

            GeoCoordinate tourLocation = new GeoCoordinate(latitude, longitude);
            double tempDist = userLocation.GetDistanceTo(tourLocation);

            //3. If the distance has decresed as per the previous value, then the user is approaching this Tour Stop, else the user might have gone
            // to some other stop and we need to find that.
            //Imp: If there are lot of tourstops, we cannot calculate the user distance from all of them continuously to find whether the user has reached 
            // to a particular tour stop
            // How we do it here  - We keep track of the tour location thats nearest to the user when he starts the tour and then keep tracking whether 
            // The user is aprroaching the tour stop. If at any point , the distance increases from the current value, then we try to see if the 
            // user is approaching any other location. We keep finding distance from the user location to the tour stops lcoation until the value keeps
            // decreasing for tour stops. If the calcualted distance increased at any time and since the list if sorted based on distance from the user location
            // initially, we know that the we have found the nearest tour stop from the user.
            // After we have found the new tour stop closer to the user, we store that inforamtion and then perform the same steps mentioned above for this new tour stop

            //User jsut started the tour
            if (currentDistanceFromTourStop == -1)
            {
                currentDistanceFromTourStop = tempDist;
                return;
            }
            else
            {
                //Moniter the user movement
                if (currentDistanceFromTourStop >= tempDist) // The user is approaching the tour stop
                {
                    //Do the necessary stuffs like check whether the user has reached the tour stop, if yes mark the tour stop as visited
                    processUserVisitToTheTourStop(tempDist);
                    currentDistanceFromTourStop = tempDist;
                }
                else
                {
                    // The user might have digressed from the path and might be approaching different tour stop for which it earlier started
                    //Calculate the new nearest tour stop

                    //Iterate through the list, to find the nearest tour stop i.e. loop only till the distance is decreasing(since the list is sorted
                    // the moment we have a greater distance tour stop, we know all the other tour stop will be far
                    double newDistance = -1;
                    int index = 0;
                    foreach (string tourStop in shortTourList)
                    {
                        //If the tour point has already been visited, dont bother calculating the distance
                        if (indexOfVisitedTourStops.Contains(index))
                        {
                            index++;
                            continue;
                        }

                        latitude = double.Parse(tourStop.Split('$')[0]);
                        longitude = double.Parse(tourStop.Split('$')[1]);

                        tourLocation = new GeoCoordinate(latitude, longitude);
                        tempDist = userLocation.GetDistanceTo(tourLocation);

                        if (newDistance == -1 || newDistance > tempDist)
                        {
                            newDistance = tempDist;
                            currentIndexOfTourPoint = index;
                            
                        }
                       // if (newDistance < tempDist)
                        //{
                            // we have found the next nearest tour stop, time to break from the loop
                         //   break;
                      //  }
                        index++;
                    }
                    currentDistanceFromTourStop = newDistance;
                    
                    //Return now, and the if condition above will take care of the condiiton when the user arrives at the tour stop
                    return;

                }
            }

        }





        /// <summary>
        /// 1. Check if the user has reached to a tour stop (The distance range is 5 mts). So if the user is around 5 mts range from the 
        /// tour stop, we pop up the message showing the short description and the user can then pick to find more details
        /// 2. If the user has reached to a place, change the push pin indicator to show as marked and calcualte the next tour point 
        /// 3. When the user moves out the range of the visited tour point, remove that push pin from the view (short tour view) but keep it marked. When 
        /// the user selects the complete view then we can show all the visited places as marked pushpin
        /// </summary>
        private void processUserVisitToTheTourStop(double userDistanceFromTourStop)
        {
            // This code executes if the user arrives at a tour stop
            //Get the pushpin and change the image to show as marked
            if (Pushpins != null && Pushpins.Count > 0)
            {
                PushpinModel pushPinModel = Pushpins[currentIndexOfTourPoint];
                if (userDistanceFromTourStop < 25) // If the distance is less than 25 meters, the user has reached the place
                {

                    //The user might revisit the tour stop
                    if (pushPinModel.isVisited == false)
                    {
                        //Replace the pushpin with the visited pushpin
                        //However, before removing the tour stop pushpin, make sure the id of the pushpin and the tourstop matches
                        // It should else something somewhere went wrong (why the hell!! :-( ) in which case we need to bruteforce check
                        // and find the tour stop 
                        //TODO: Handle the above case

                        pushPinModel.Icon = new Uri("/Resources/Icons/ApplicationBar/Precise_Location.png", UriKind.Relative);
                        pushPinModel.isVisited = true;
                        pushPinModel.Visited = "Green";
                        pushPinModel.Opacity = "0.5";

                        
   
                        indexOfVisitedTourStops.Add(currentIndexOfTourPoint);

                       
                        //After the tour stop has been marked as visited, recalcualte the distance and index if the next
                        // nearest stop
                        // Good thing :-), this will automatically been done by the above funtion as the user move out of the 
                        // Range(5mts) from the current visited tour stop
                      
                    }
                }
                
            }
            

            
        }

        
        #endregion

        
        

        
    }
}
